#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

from common.const import RestoreTypeEnum
from common.logger import Logger
from db2.comm.const import Db2Const
from db2.comm.const import Db2JsonConstant
from db2.comm.error_code import Db2ErrCode
from db2.comm.util.common_util import Db2CommonUtil
from db2.comm.util.param_util import Db2ParamUtil
from db2.comm.util.single_util import SingleUtil
from db2.restore.hadr.hadr_restore import HadrRestore
from db2.restore.hadr.hadr_ts_restore_service import HadrTsRestoreService

LOGGER = Logger().get_logger(filename="db2.log")


class HadrTsRestore(HadrRestore):
    def __init__(self, task_name, pid, job_id, sub_job_id, param_dict):
        super().__init__(task_name, pid, job_id, sub_job_id, param_dict)
        self.service = None
        self.init_service()
        self.restore_type_dict = {}
        self.init_restore_type_dict()

    def init_service(self):
        self.service = HadrTsRestoreService(self.parse_param)

    def init_restore_type_dict(self):
        self.restore_type_dict = {
            RestoreTypeEnum.FULL_RESTORE.value: self.full_restore,
            RestoreTypeEnum.DIFF_RESTORE.value: self.diff_restore,
            RestoreTypeEnum.INCRE_RESTORE.value: self.incr_restore,
        }

    def check_resource(self):
        if not self.service.check_node_is_primary():
            LOGGER.info("The standby node does not need to be checked.")
            return True
        check_steps = [
            self.check_table_space_exist,
            self.check_table_in_tablespace
        ]
        for step in check_steps:
            if not step():
                LOGGER.error(f"Failed to execute ts(check) cmd.{self.parse_param.get_log_comm()}")
                return False
        LOGGER.info(f"Execute restore ts(check) completed.{self.parse_param.get_log_comm()}")
        return True

    def full_restore(self):
        LOGGER.info("Start to execute full ts restore.")
        # 执行恢复操作
        if not self.service.exec_ts_full_restore():
            LOGGER.error(f"Failed to execute the restore(full).{self.parse_param.get_log_comm()}")
            self.action_result.body_err = Db2ErrCode.USER_BACKUP_ERROR
            self.action_result.message = "Failed to execute the restore"
            return False
        LOGGER.info("Execute db restore(full) ts successfully.")
        return True

    def incr_restore(self):
        LOGGER.info("Start to execute incr ts restore.")
        if not self.service.exec_ts_incr_restore():
            LOGGER.error(f"Failed to execute the restore(incr).{self.parse_param.get_log_comm()}")
            self.action_result.body_err = Db2ErrCode.USER_BACKUP_ERROR
            self.action_result.message = "Failed to execute the restore"
            return False
        LOGGER.info("Execute db restore(incr) ts successfully.")
        return True

    def diff_restore(self):
        LOGGER.info(f"Start to execute diff ts restore.{self.parse_param.get_log_comm()}")
        if not self.service.exec_ts_diff_restore():
            LOGGER.error(f"Failed to execute the restore(diff).{self.parse_param.get_log_comm()}")
            self.action_result.body_err = Db2ErrCode.USER_BACKUP_ERROR
            self.action_result.message = "Failed to execute the restore"
            return False
        LOGGER.info("Execute db restore(diff) ts successfully.")
        return True

    def exec_rollforward(self):
        LOGGER.info(f"Start to execute ts rollforward.{self.parse_param.get_log_comm()}")
        if not self.service.exec_ts_rollforward():
            LOGGER.error(f"Failed to execute the database rollforward.{self.parse_param.get_log_comm()}")
            self.action_result.body_err = Db2ErrCode.USER_BACKUP_ERROR
            self.action_result.message = "Failed to execute the database rollforward"
            return False

        if not self.service.exec_ts_offline_backup():
            LOGGER.error(f"Failed to execute the ts backup.{self.parse_param.get_log_comm()}")
            return False
        LOGGER.info("Execute rollforward successfully.")
        return True

    def check_table_space_exist(self):
        tgt_ts_names = Db2ParamUtil.get_ts_name_for_restore(self.param_dict)
        all_ts_dict = Db2CommonUtil.get_table_space_info_dict_of_db(self.service.os_user, self.service.db_name)
        if not tgt_ts_names or not set(tgt_ts_names).issubset(all_ts_dict.keys()):
            LOGGER.error(f"The tablespaces({tgt_ts_names}) are inconsistent or do not exist.")
            err_param = SingleUtil.build_table_space_not_exists_message(set(all_ts_dict.keys()), tgt_ts_names)
            self.action_result.body_err = Db2ErrCode.SPACE_NOT_EXISTS
            self.action_result.body_err_params = [err_param]
            self.action_result.message = "The table spaces of the database does not exists"
            return False
        chk_bak_ts_state_ret = list(all_ts_dict.get(i, {}).get("state", "") == Db2Const.NORMAL_STATE
                                    for i in tgt_ts_names)
        if not all(chk_bak_ts_state_ret):
            LOGGER.error(f"There are tablespaces whose state is not normal in the backup tablespaces, "
                         f"database name: {self.service.db_name}.{self.parse_param.get_log_comm()}")
            err_param = SingleUtil.build_table_space_not_exists_message(set(all_ts_dict.keys()), tgt_ts_names)
            self.action_result.body_err = Db2ErrCode.SPACE_NOT_EXISTS
            self.action_result.body_err_params = [err_param]
            return False
        LOGGER.info("Check tablespace exist succeeded.")
        return True

    def check_table_in_tablespace(self):
        # 表空间下表被删除掉
        src_db_name = Db2ParamUtil.get_db_name_of_ts_copy(self.param_dict)
        tgt_ts_names = Db2ParamUtil.get_ts_name_for_restore(self.param_dict)
        table_name_drop = []
        table_spaces_str = self.param_dict.get("job", {}).get("targetObject", {}).get("extendInfo", {}).get("table", "")
        table_from_restore, table_in_restore = Db2CommonUtil.check_table_from_tablespace(self.service.os_user,
                                                                                         src_db_name, tgt_ts_names)
        path = Db2ParamUtil.get_copy_path_info(self.param_dict)
        if not path:
            LOGGER.error(f"Param of copy invalid.{self.parse_param.get_log_comm()}")
            self.action_result.message = "Param of copy invalid"
            self.action_result.body_err = Db2ErrCode.ERROR_COMMON_INVALID_PARAMETER
            return False
        backup_table = path[0].get(Db2JsonConstant.BACKUP_TABLE)
        for table_name in backup_table:
            if table_name not in table_in_restore:
                table_name_drop.append(table_name)
        if table_name_drop:
            LOGGER.error(f"The table({table_name_drop}) in the copy has been deleted.{self.parse_param.get_log_comm()}")
            self.action_result.message = "Table is deleted"
            self.action_result.body_err = Db2ErrCode.TABLESPACES_DROP_TABLE
            self.action_result.body_err_params = [table_name_drop[0], table_spaces_str, table_name_drop[0]]
            return False
        LOGGER.info("Check table in ts succeeded.")
        return True
